home *** CD-ROM | disk | FTP | other *** search
Text File | 2002-08-08 | 40.6 KB | 1,835 lines |
- /*
- ** Message.m
- **
- ** Copyright (c) 2001, 2002
- **
- ** Author: Ludovic Marcotte <ludovic@Sophos.ca>
- **
- ** This library is free software; you can redistribute it and/or
- ** modify it under the terms of the GNU Lesser General Public
- ** License as published by the Free Software Foundation; either
- ** version 2.1 of the License, or (at your option) any later version.
- **
- ** This library is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ** Lesser General Public License for more details.
- **
- ** You should have received a copy of the GNU Lesser General Public
- ** License along with this library; if not, write to the Free Software
- ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
- #import <Pantomime/Message.h>
-
- #import <Pantomime/Constants.h>
- #import <Pantomime/Folder.h>
- #import <Pantomime/NSStringExtensions.h>
- #import <Pantomime/NSDataExtensions.h>
-
- #include <time.h>
-
- @implementation Message
-
- //
- //
- //
- - (id) init
- {
- self = [super init];
-
- // We initialize our recipiens array
- recipients = [[NSMutableArray alloc] init];
-
- // We initialize our dictionary that will hold all our headers
- // We initialize it with a capacity of 25. This is a empirical number
- // that is used to speedup the addition of headers w/o reallocating
- // our array everytime we add a new element.
- headers = [[NSMutableDictionary alloc] initWithCapacity: 25];
-
- // We initialize our headers with some basic values in case they are missing in the messages we decode
- [headers setObject: @"text/plain" forKey: @"Content-Type"];
-
- // FIXME: Should we always initialize with RECENT? or just nothing?
- flags = [[Flags alloc] initWithFlags: RECENT];
-
- // By default, we want the subclass's rawSource method to be called so we set our
- // rawSource ivar to nil. If it's not nil (ONLY set in initWithString), it'll be returned.
- // for performances improvements.
- rawSource = nil;
-
- // We initialize our dictionary holding all extra properties a message might have
- extraProperties = [[NSMutableDictionary alloc] init];
-
- // Initially, we don't have any references
- [self setReferences: nil];
-
-
- [self setInitialized: NO];
- [self setSize: 0];
-
- return self;
- }
-
-
- //
- //
- //
- - (id) initWithData: (NSData *) theData
- {
- NSRange aRange;
-
- aRange = [theData rangeOfCString: "\n\n"];
-
- if (aRange.length == 0)
- {
- NSDebugLog(@"Message: failed to initialize message from data.");
- AUTORELEASE(self);
- return nil;
- }
-
- // We initialize our message with the headers and the content
- self = [self init];
-
- [self setHeadersFromData: [theData subdataWithRange: NSMakeRange(0,aRange.location)]];
- [self setContentFromRawSource:
- [theData subdataWithRange:
- NSMakeRange(aRange.location + 2, [theData length]-(aRange.location+2))]];
-
-
- //
- // We can tell now that this message is fully initialized
- // NOTE: We must NOT call [self setInitialize: YES] since
- // it will call the method from the subclass and may do
- // extremely weird things.
- initialized = YES;
-
- // We set our rawSource ivar for performances reason
- [self setRawSource: theData];
-
- return self;
- }
-
-
- //
- //
- //
- - (id) initWithData: (NSData *) theData
- charset: (NSString *) theCharset
- {
- [self setDefaultCharset: theCharset];
-
- return [self initWithData: theData];
- }
-
-
- //
- //
- //
- - (id) initWithHeadersFromData: (NSData *) theHeaders
- {
- self = [self init];
-
- [self setHeadersFromData: theHeaders];
-
- return self;
- }
-
-
- //
- //
- //
- - (id) initWithHeaders: (NSDictionary *) theHeaders
- {
- self = [self init];
-
- [self setHeaders: theHeaders];
-
- return self;
- }
-
-
- //
- //
- //
- - (void) dealloc
- {
- //NSDebugLog(@"Message: -dealloc");
-
- RELEASE(recipients);
- RELEASE(headers);
- RELEASE(flags);
-
- RELEASE(extraProperties);
-
- RELEASE(references);
-
- TEST_RELEASE(rawSource);
-
- [super dealloc];
- }
-
-
- //
- // NSCoding protocol
- //
- - (void) encodeWithCoder: (NSCoder *) theCoder
- {
- [super encodeWithCoder: theCoder];
-
- [theCoder encodeObject: [self recipients]];
- [theCoder encodeObject: [self allHeaders]];
- [theCoder encodeObject: [NSNumber numberWithInt: [self messageNumber]]];
- [theCoder encodeObject: [self flags]];
- }
-
-
- //
- //
- //
- - (id) initWithCoder: (NSCoder *) theCoder
- {
- self = [super init];
-
- [super initWithCoder: theCoder];
-
- [self setRecipients: [theCoder decodeObject]];
- [self setHeaders: [theCoder decodeObject]];
- [self setMessageNumber: [[theCoder decodeObject] intValue]];
- [self setFlags: [theCoder decodeObject]];
-
- // It's very important to set this ivar to NO since we don't serialize the content
- // or our message.
- initialized = NO;
-
- // We initialize the rest of our ivars
- folder = nil;
- rawSource = nil;
-
- // We initialize our dictionary holding all extra properties a message might have
- extraProperties = [[NSMutableDictionary alloc] init];
-
- return self;
- }
-
-
- //
- //
- //
- - (InternetAddress *) from
- {
- return [headers objectForKey: @"From"];
- }
-
-
- //
- //
- //
- - (void) setFrom: (InternetAddress *) theInternetAddress
- {
- [headers setObject: theInternetAddress
- forKey: @"From"];
- }
-
-
- //
- //
- //
- - (int) messageNumber
- {
- return messageNumber;
- }
-
-
- //
- //
- //
- - (void) setMessageNumber: (int) theMessageNumber
- {
- messageNumber = theMessageNumber;
- }
-
-
- //
- //
- //
- - (NSString *) messageID
- {
- return [headers objectForKey: @"Message-ID"];
- }
-
-
- //
- //
- //
- - (void) setMessageID: (NSString *) theMessageID
- {
- [headers setObject: theMessageID
- forKey: @"Message-ID"];
- }
-
-
- //
- //
- //
- - (NSCalendarDate *) receivedDate
- {
- return [headers objectForKey: @"Date"];
- }
-
-
- //
- //
- //
- - (void) setReceivedDate: (NSCalendarDate*) theDate
- {
- [headers setObject: theDate
- forKey: @"Date"];
- }
-
-
- //
- //
- //
- - (void) addToRecipients: (InternetAddress *) theAddress
- {
- if ( theAddress )
- {
- [recipients addObject: theAddress];
- }
- }
-
-
- //
- //
- //
- - (void) removeFromRecipients: (InternetAddress *) theAddress
- {
- if ( theAddress )
- {
- [recipients removeObject: theAddress];
- }
- }
-
-
- //
- //
- //
- - (NSArray *) recipients
- {
- return recipients;
- }
-
-
- //
- //
- //
- - (void) setRecipients: (NSArray *) theRecipients
- {
- if ( theRecipients )
- {
- NSMutableArray *newRecipients;
-
- newRecipients = [NSMutableArray arrayWithArray: theRecipients];
- RELEASE(recipients);
- RETAIN(newRecipients);
- recipients = newRecipients;
- }
- else
- {
- RELEASE(recipients);
- recipients = nil;
- }
- }
-
-
- //
- //
- //
- - (int) recipientsCount
- {
- return [[self recipients] count];
- }
-
-
- //
- //
- //
- - (void) removeAllRecipients
- {
- [recipients removeAllObjects];
- }
-
-
- //
- //
- //
- - (InternetAddress *) replyTo
- {
- return [headers objectForKey: @"Reply-To"];
- }
-
-
- //
- //
- //
- - (void) setReplyTo: (InternetAddress *) theInternetAddress
- {
- if ( theInternetAddress )
- {
- [headers setObject: theInternetAddress
- forKey: @"Reply-To"];
- }
- else
- {
- [headers removeObjectForKey: @"Reply-To"];
- }
- }
-
-
- //
- //
- //
- - (NSString *) subject
- {
- return [headers objectForKey: @"Subject"];
- }
-
-
- //
- //
- //
- - (void) setSubject: (NSString *) theSubject
- {
- [headers setObject: theSubject
- forKey: @"Subject"];
- }
-
-
- //
- //
- //
- - (BOOL) isInitialized
- {
- return initialized;
- }
-
-
- //
- //
- //
- - (void) setInitialized: (BOOL) aBOOL
- {
- initialized = aBOOL;
- }
-
-
- //
- // Implementation of the Part protocol follows...
- //
-
-
- - (NSString *) contentDisposition
- {
- return [headers objectForKey: @"Content-Disposition"];
- }
-
- - (void) setContentDisposition: (NSString *) theContentDisposition
- {
- [super setContentDisposition: theContentDisposition];
-
- [headers setObject: theContentDisposition
- forKey: @"Content-Disposition"];
- }
-
- //
- //
- //
- - (NSString *) contentID
- {
- return [headers objectForKey: @"Content-Id"];
- }
-
-
- //
- //
- //
- - (void) setContentID: (NSString *) theContentID
- {
- [super setContentID: theContentID];
- [headers setObject: theContentID
- forKey: @"Content-Id"];
- }
-
-
- //
- //
- //
- - (NSString *) contentType
- {
- return [headers objectForKey: @"Content-Type"];
- }
-
-
- //
- //
- //
- - (void) setContentType: (NSString*) theContentType
- {
- [super setContentType: theContentType];
-
- [headers setObject: theContentType
- forKey: @"Content-Type"];
- }
-
- //
- // See RFC1521 (MIME) for more details.
- // The Content-Type MUST be defined BEFORE calling this function otherwise
- // the content will be considered as a pure string.
- //
- - (void) setContentFromRawSource: (NSData *) theData
- {
- [MimeUtility setContentFromRawSource: theData
- inPart: self];
- }
-
- //
- //
- //
- - (NSString *) organization
- {
- return [headers objectForKey: @"Organization"];
- }
-
-
- //
- //
- //
- - (void) setOrganization: (NSString *) theOrganization
- {
- [headers setObject: theOrganization
- forKey: @"Organization"];
- }
-
-
- //
- //
- //
- - (id) extraPropertyForKey: (id) theKey
- {
- return [extraProperties objectForKey: theKey];
- }
-
-
- //
- //
- //
- - (void) setExtraProperty: (id) theProperty
- forKey: (id) theKey
- {
- [extraProperties setObject: theProperty
- forKey: theKey];
- }
-
-
- //
- //
- //
- - (NSArray *) allReferences
- {
- return references;
- }
-
-
- //
- //
- //
- - (void) setReferences: (NSArray *) theReferences
- {
- if ( theReferences )
- {
- RETAIN(theReferences);
- RELEASE(references);
- references = theReferences;
- }
- else
- {
- DESTROY(references);
- }
- }
-
-
- //
- //
- //
- - (Flags *) flags
- {
- return flags;
- }
-
-
- //
- //
- //
- - (void) setFlags: (Flags*) theFlags
- {
- RELEASE(flags);
- flags = RETAIN(theFlags);
- }
-
-
- //
- //
- //
- - (NSString *) mimeVersion
- {
- return [headers objectForKey: @"MIME-Version"];
- }
-
-
- //
- //
- //
- - (void) setMimeVersion: (NSString *) theMimeVersion
- {
- [headers setObject: theMimeVersion
- forKey: @"MIME-Version"];
- }
-
-
- //
- // The message will NOT have a content other than the PLAIN/TEXT one(s).
- //
- - (Message *) replyWithReplyToAll: (BOOL) flag
- {
- InternetAddress *anInternetAddress;
- Message *theMessage;
-
- NSMutableString *aMutableString;
- id aContent;
- int i;
-
- BOOL needsToQuote = NO;
-
- theMessage = [[Message alloc] init];
-
- // We set the subject of our message
- if ( [[[self subject] lowercaseString] hasPrefix: @"re"] )
- {
- [theMessage setSubject: [self subject] ];
- }
- else
- {
- [theMessage setSubject: [NSString stringWithFormat: @"Re: %@", [self subject]] ];
- }
-
- // If Reply-To is defined, we use it. Otherwise, we use From:
- if ( [self replyTo] == nil )
- {
- anInternetAddress = [self from];
- }
- else
- {
- anInternetAddress = [self replyTo];
- }
-
- [anInternetAddress setType: TO];
- [theMessage addToRecipients: anInternetAddress];
-
- // We add our In-Reply-To header
- if ( [self messageID] )
- {
- [theMessage addHeader: @"In-Reply-To"
- withValue: [self messageID]];
- }
-
- // If we reply to all, we add the other recipients
- if ( flag )
- {
- NSEnumerator *anEnumerator;
-
- anEnumerator = [[self recipients] objectEnumerator];
-
- while ((anInternetAddress = [anEnumerator nextObject]))
- {
- [anInternetAddress setType: CC];
- [theMessage addToRecipients: anInternetAddress];
- }
- }
-
- // We finally work on the content of the message
- aMutableString = [[NSMutableString alloc] init];
- [aMutableString appendString: @"On "];
- [aMutableString appendString: [[self receivedDate] description] ];
- [aMutableString appendString: [NSString stringWithFormat:@" %@", [[self from] unicodeStringValue]]];
- [aMutableString appendString: @" wrote:\n\n"];
-
- // We give a default value to aContent
- aContent = nil;
-
- //
- // We now get the right text part of the message.
- //
- // If it's a text/enriched or a text/html, we must remove all the
- // formatting codes and keep only the 'real' text contained in that part.
- //
- if ( [self isMimeType: @"text": @"*"] )
- {
- aContent = (NSString *)[self content];
- needsToQuote = YES;
- }
- //
- // If our message only contains the following part types, we cannot
- // represent those in a reply.
- //
- else if ( [self isMimeType: @"application": @"*"] ||
- [self isMimeType: @"audio": @"*"] ||
- [self isMimeType: @"image": @"*"] ||
- [self isMimeType: @"message": @"*"] ||
- [self isMimeType: @"video": @"*"] )
- {
- aContent = [NSString stringWithString: @"\t[NON-Text Body part not included]"];
- needsToQuote = NO;
- }
- //
- // We have a multipart type. It can be:
- //
- // multipart/appledouble, multipart/alternative, multipart/related,
- // multipart/mixed or even multipart/report.
- //
- // We must search for a text part to use in our reply.
- //
- else if ( [self isMimeType: @"multipart" : @"*"] )
- {
- MimeMultipart *aMimeMultipart;
- MimeBodyPart *aMimeBodyPart;
-
- aMimeMultipart = (MimeMultipart *)[self content];
-
- for (i = 0; i < [aMimeMultipart count]; i++)
- {
- aMimeBodyPart = [aMimeMultipart bodyPartAtIndex: i];
-
- //
- // We do a full verification on the Content-Type since we might
- // have a text/x-{something} like text/x-vcard.
- //
- if ( [aMimeBodyPart isMimeType: @"text" : @"plain"] ||
- [aMimeBodyPart isMimeType: @"text" : @"enriched"] ||
- [aMimeBodyPart isMimeType: @"text" : @"html"] )
- {
- // If our part was base64 encoded, we must convert the
- // NSData object into a NSString
- if ( [aMimeBodyPart contentTransferEncoding] == BASE64 &&
- [[aMimeBodyPart content] isKindOfClass: [NSData class]] )
- {
- aContent = [[NSString alloc] initWithData: (NSData *)[aMimeBodyPart content]
- encoding: NSASCIIStringEncoding];
- AUTORELEASE(aContent);
- }
- else
- {
- aContent = (NSString*)[aMimeBodyPart content];
- }
- break;
- }
- //
- // If we have a multipart/alternative contained in our multipart/mixed (or related)
- // we find the text/plain part contained in this multipart/alternative object.
- //
- else if ( [aMimeBodyPart isMimeType: @"multipart" : @"alternative"] )
- {
- MimeMultipart *anOtherMimeMultipart;
- int j;
-
- anOtherMimeMultipart = (MimeMultipart *)[aMimeBodyPart content];
-
- for (j = 0; j < [anOtherMimeMultipart count]; j++)
- {
- aMimeBodyPart = [anOtherMimeMultipart bodyPartAtIndex: i];
-
- if ( [aMimeBodyPart isMimeType: @"text" : @"plain"] ||
- [aMimeBodyPart isMimeType: @"text" : @"enriched"] ||
- [aMimeBodyPart isMimeType: @"text" : @"html"] )
- {
- // If our part was base64 encoded, we must convert the
- // NSData object into a NSString
- if ( [aMimeBodyPart contentTransferEncoding] == BASE64 )
- {
- aContent = [[NSString alloc] initWithData: (NSData *)[aMimeBodyPart content]
- encoding: NSASCIIStringEncoding];
- AUTORELEASE(aContent);
- }
- else
- {
- aContent = (NSString *)[aMimeBodyPart content];
- }
- break;
- }
- }
- } // else if (...)
- }
-
- needsToQuote = YES;
- }
-
- //
- // It was impossible for use to find a text/plain part. Let's
- // inform our user that we can't do anything with this message.
- //
- if ( !aContent || [aContent isKindOfClass: [NSData class]])
- {
- aContent = [NSString stringWithString: @"\t[NON-Text Body part not included]"];
- needsToQuote = NO;
- }
- else
- {
- // We remove the signature
- NSRange aRange;
-
- aRange = [aContent rangeOfString: @"\n-- "
- options: NSBackwardsSearch];
-
- // We found it!
- if ( aRange.length )
- {
- aContent = [aContent substringToIndex: aRange.location];
- }
- }
-
- // We now have our content as string, let's 'quote' it
- if ( aContent && needsToQuote )
- {
- aContent = [MimeUtility unwrapPlainTextString: aContent
- usingQuoteWrappingLimit: 78];
- [aMutableString appendString: [MimeUtility quotePlainTextString: aContent
- quoteLevel: 1
- wrappingLimit: 80]];
- }
- else if (aContent && !needsToQuote)
- {
- [aMutableString appendString: aContent];
- }
-
- [theMessage setContent: aMutableString];
-
- RELEASE(aMutableString);
-
- return AUTORELEASE(theMessage);
- }
-
-
- //
- // The message WILL have a content.
- //
- - (Message *) forward
- {
- NSMutableString *aMutableString;
- Message *theMessage;
-
- theMessage = [[Message alloc] init];
-
- // We set the subject of our message
- [theMessage setSubject: [NSString stringWithFormat:@"%@ (fwd)", [self subject]] ];
-
- // We create our generic forward message header
- aMutableString = [[NSMutableString alloc] init];
- AUTORELEASE(aMutableString);
-
- [aMutableString appendString: @"---------- Forwarded message ----------\n"];
- [aMutableString appendString: @"Date: "];
- [aMutableString appendString: [[self receivedDate] description] ];
- [aMutableString appendString: @"\nFrom: "];
- [aMutableString appendString: [[self from] unicodeStringValue]];
- [aMutableString appendString: @"\nSubject: "];
- [aMutableString appendString: [NSString stringWithFormat:@"%@\n\n", [self subject]] ];
-
- //
- // If our Content-Type is text/plain, we represent it as a it is, otherwise,
- // we currently create a new body part representing the forwarded message.
- //
- if ( [self isMimeType: @"text": @"*"] )
- {
- // Our message is a text/plain one
- if ( [self isMimeType: @"text" : @"plain"] )
- {
- // We set the content of our message
- [aMutableString appendString: (NSString*)[self content]];
-
- // We set the Content-Transfer-Encoding and the Charset to the previous one
- [theMessage setContentTransferEncoding: [self contentTransferEncoding]];
- [theMessage setCharset: [self charset]];
-
- [theMessage setContent: aMutableString];
- [theMessage setSize: [aMutableString length]];
- }
- // Our message is either a text/enriched, text/html or something else (but text/*).
- else
- {
- MimeMultipart *aMimeMultipart;
- MimeBodyPart *aMimeBodyPart;
-
- aMimeMultipart = [[MimeMultipart alloc] init];
-
- // We add our text/plain part (using Part's default initialized values)
- aMimeBodyPart = [[MimeBodyPart alloc] init];
- [aMimeBodyPart setContent: aMutableString];
- [aMimeBodyPart setContentDisposition: @"inline"];
- [aMimeBodyPart setSize: [aMutableString length]];
- [aMimeMultipart addBodyPart: aMimeBodyPart];
- RELEASE(aMimeBodyPart);
-
- // We add our other text part as a attachment
- aMimeBodyPart = [[MimeBodyPart alloc] init];
- [aMimeBodyPart setContentType: [self contentType]];
- [aMimeBodyPart setContent: [self content]];
- [aMimeBodyPart setContentTransferEncoding: [self contentTransferEncoding]];
- [aMimeBodyPart setCharset: [self charset]];
- [aMimeBodyPart setContentDisposition: @"attachment"];
- [aMimeBodyPart setSize: [self size]];
- [aMimeMultipart addBodyPart: aMimeBodyPart];
- RELEASE(aMimeBodyPart);
-
- [theMessage setContent: aMimeMultipart];
- RELEASE(aMimeMultipart);
- }
- }
- //
- // If our Content-Type is a message/rfc822 or any other type like
- // application/*, audio/*, image/* or video/*
- //
- else if ( [self isMimeType: @"application": @"*"] ||
- [self isMimeType: @"audio": @"*"] ||
- [self isMimeType: @"image": @"*"] ||
- [self isMimeType: @"message": @"*"] ||
- [self isMimeType: @"video": @"*"] )
- {
- MimeMultipart *aMimeMultipart;
- MimeBodyPart *aMimeBodyPart;
-
- aMimeMultipart = [[MimeMultipart alloc] init];
-
- // We add our text/plain part.
- aMimeBodyPart = [[MimeBodyPart alloc] init];
- [aMimeBodyPart setContent: aMutableString];
- [aMimeBodyPart setContentDisposition: @"inline"];
- [aMimeBodyPart setSize: [aMutableString length]];
- [aMimeMultipart addBodyPart: aMimeBodyPart];
- RELEASE(aMimeBodyPart);
-
- // We add our content as an attachment
- aMimeBodyPart = [[MimeBodyPart alloc] init];
- [aMimeBodyPart setContentType: [self contentType]];
- [aMimeBodyPart setContent: [self content]];
- [aMimeBodyPart setContentTransferEncoding: [self contentTransferEncoding]];
- [aMimeBodyPart setContentDisposition: @"attachment"];
- [aMimeBodyPart setCharset: [self charset]];
- [aMimeBodyPart setFilename: [self filename]];
- [aMimeBodyPart setSize: [self size]];
- [aMimeMultipart addBodyPart: aMimeBodyPart];
- RELEASE(aMimeBodyPart);
-
- [theMessage setContent: aMimeMultipart];
- RELEASE(aMimeMultipart);
- }
- //
- // We have a multipart object. We must treat multipart/alternative
- // parts differently since we don't want to include multipart parts in the forward.
- //
- else if ( [self isMimeType: @"multipart" : @"*"] )
- {
- //
- // If we have multipart/alternative part, we only keep one part from it.
- //
- if ( [self isMimeType: @"multipart" : @"alternative"] )
- {
- MimeMultipart *aMimeMultipart;
- MimeBodyPart *aMimeBodyPart;
- int i;
-
- aMimeMultipart = (MimeMultipart *)[self content];
- aMimeBodyPart = nil;
-
- // We search for our text/plain part
- for (i = 0; i < [aMimeMultipart count]; i++)
- {
- aMimeBodyPart = [aMimeMultipart bodyPartAtIndex: i];
-
- if ( [aMimeBodyPart isMimeType: @"text": @"plain"] )
- {
- break;
- }
- else
- {
- aMimeBodyPart = nil;
- }
- }
-
- // We found one
- if ( aMimeBodyPart )
- {
- NSString *aString;
-
- // If our part was base64 encoded, we must convert the
- // NSData object into a NSString
- if ( [aMimeBodyPart contentTransferEncoding] == BASE64 &&
- [[aMimeBodyPart content] isKindOfClass: [NSData class]] )
- {
- aString = [[NSString alloc] initWithData: (NSData *)[aMimeBodyPart content]
- encoding: NSASCIIStringEncoding];
- AUTORELEASE(aString);
- }
- else
- {
- aString = (NSString*)[aMimeBodyPart content];
- }
-
- // We set the content of our message
- [aMutableString appendString: aString];
-
- // We set the Content-Transfer-Encoding and the Charset to our text part
- [theMessage setContentTransferEncoding: [aMimeBodyPart contentTransferEncoding]];
- [theMessage setCharset: [aMimeBodyPart charset]];
-
- [theMessage setContent: aMutableString];
- [theMessage setSize: [aMutableString length]];
- }
- // We haven't found one! Inform the user that it happened.
- else
- {
- [aMutableString appendString: @"No text/plain part from this multipart/alternative part has been found"];
- [aMutableString appendString: @"\nNo parts have been included as attachments with this mail during the forward operation."];
- [aMutableString appendString: @"\n\nPlease report this as a bug."];
-
- [theMessage setContent: aMutableString];
- [theMessage setSize: [aMutableString length]];
- }
- }
- //
- // We surely have a multipart/mixed or multipart/related.
- // We search for a text/plain part inside our multipart object.
- // We 'keep' the other parts in a separate new multipart object too
- // that will become our new content.
- //
- else
- {
- MimeMultipart *aMimeMultipart, *newMimeMultipart;
- MimeBodyPart *aMimeBodyPart;
- BOOL hasFoundTextPlain = NO;
- int i;
-
- // We get our current mutipart object
- aMimeMultipart = (MimeMultipart *)[self content];
-
- // We create our new multipart object for holding all our parts.
- newMimeMultipart = [[MimeMultipart alloc] init];
-
- for (i = 0; i < [aMimeMultipart count]; i++)
- {
- aMimeBodyPart = [aMimeMultipart bodyPartAtIndex: i];
-
- if ( [aMimeBodyPart isMimeType: @"text": @"plain"]
- && !hasFoundTextPlain )
- {
- MimeBodyPart *newMimeBodyPart;
-
- newMimeBodyPart = [[MimeBodyPart alloc] init];
-
- // We set the content of our new part
- [aMutableString appendString: (NSString*)[aMimeBodyPart content]];
- [newMimeBodyPart setContent: aMutableString];
- [newMimeBodyPart setSize: [aMutableString length]];
-
- // We set the Content-Transfer-Encoding and the Charset to the previous one
- [newMimeBodyPart setContentTransferEncoding: [aMimeBodyPart contentTransferEncoding]];
- [newMimeBodyPart setCharset: [aMimeBodyPart charset]];
-
- // We finally add our new part to our MIME multipart object
- [newMimeMultipart addBodyPart: newMimeBodyPart];
- RELEASE(newMimeBodyPart);
-
- hasFoundTextPlain = YES;
- }
- // We set the Content-Disposition to "attachment"
- // all the time.
- else
- {
- [aMimeBodyPart setContentDisposition: @"attachment"];
- [newMimeMultipart addBodyPart: aMimeBodyPart];
- }
- }
-
- [theMessage setContent: newMimeMultipart];
- RELEASE(newMimeMultipart);
- }
- }
- //
- // We got an unknown part. Let's inform the user about this situation.
- //
- else
- {
- // We set the content of our message
- [aMutableString appendString: @"The original message contained an unknown part that was not included in this forward message."];
- [aMutableString appendString: @"\n\nPlease report this as a bug."];
-
- [theMessage setContent: aMutableString];
- [theMessage setSize: [aMutableString length]];
- }
-
- return AUTORELEASE(theMessage);
- }
-
-
- //
- //
- //
- - (NSData *) dataUsingSendingMode : (int) theMode
- {
- NSMutableData *aMutableData;
- NSDictionary *aLocale;
-
- NSEnumerator *allHeaderKeyEnumerator;
- NSString *aKey;
-
- NSCalendarDate *aCalendarDate;
-
- NSData *aBoundary, *aData;
-
- char *lineTerminator;
-
-
- if ( theMode == SEND_USING_SMTP )
- {
- lineTerminator = CRLF;
- }
- else
- {
- lineTerminator = LF;
- }
-
- // We get our locale in English
- #ifndef MACOSX
- aLocale = [NSDictionary dictionaryWithContentsOfFile: [NSBundle pathForGNUstepResource: @"English"
- ofType: nil
- inDirectory: @"Resources/Languages"] ];
- #else
- aLocale = [NSDictionary dictionaryWithContentsOfFile: [[NSBundle bundleForClass:[NSObject class]]
- pathForResource: @"English"
- ofType: nil
- inDirectory: @"Languages"] ];
- #endif
-
- // We initialize our mutable data object holding the raw data of the
- // new message.
- aMutableData = [[NSMutableData alloc] init];
-
- aBoundary = [MimeUtility generateBoundary];
-
- // If we're sending this message to a local folder, we must append the From - tag.
- if ( theMode == SEND_TO_FOLDER )
- {
- [aMutableData appendCFormat: @"From -\n"];
- }
-
- #ifndef MACOSX
- tzset();
-
- aCalendarDate = [[[NSDate alloc] init] dateWithCalendarFormat:@"%a, %d %b %Y %H:%M:%S %z"
- timeZone: [NSTimeZone timeZoneWithAbbreviation:
- [NSString stringWithCString: tzname[1]]] ];
- #else
- aCalendarDate = [[[NSDate alloc] init] dateWithCalendarFormat:@"%a, %d %b %Y %H:%M:%S %z"
- timeZone: [NSTimeZone systemTimeZone] ];
- #endif
- [aMutableData appendCFormat: @"Date: %@%s", [aCalendarDate descriptionWithLocale: aLocale],
- lineTerminator];
-
- // We set the subject, if we have one!
- if ( [[[self subject] stringByTrimmingWhiteSpaces] length] > 0 )
- {
- [aMutableData appendCString: "Subject: "];
- [aMutableData appendData: [MimeUtility encodeWordUsingQuotedPrintable: [self subject]
- prefixLength: 8]];
- [aMutableData appendCString: lineTerminator];
- }
-
- // We set our Message-ID
- [aMutableData appendCFormat:@"Message-ID: <"];
- [aMutableData appendData: [MimeUtility generateOSID]];
- [aMutableData appendCFormat: @">%s", lineTerminator];
-
- [aMutableData appendCFormat: @"MIME-Version: 1.0 (Generated by Pantomime %@)%s", PANTOMIME_VERSION, lineTerminator];
-
- // We encode our From: field
- [aMutableData appendCFormat: @"From: "];
- [aMutableData appendData: [[self from] dataValue]];
- [aMutableData appendCFormat: @"%s",lineTerminator];
-
- // We encode our To field
- aData = [self _formatRecipientsWithType: TO];
-
- if ( aData )
- {
- [aMutableData appendCString: "To: "];
- [aMutableData appendData: aData];
- [aMutableData appendCString: lineTerminator];
- }
-
- // We encode our Cc field
- aData = [self _formatRecipientsWithType: CC];
-
- if ( aData )
- {
- [aMutableData appendCString: "Cc: "];
- [aMutableData appendData: aData];
- [aMutableData appendCString: lineTerminator];
- }
-
- // We encode our Bcc field
- aData = [self _formatRecipientsWithType: BCC];
-
- if ( aData )
- {
- [aMutableData appendCString: "Bcc: "];
- [aMutableData appendData: aData];
- [aMutableData appendCString: lineTerminator];
- }
-
- // We set the Reply-To address in case we need to
- if ( [self replyTo] )
- {
- [aMutableData appendCFormat: @"Reply-To: "];
- [aMutableData appendData: [[self replyTo] dataValue]];
- [aMutableData appendCFormat: @"%s",lineTerminator];
- }
-
- // We set the Organization header value if we need to
- // FIXME: Should we quote this value?
- if ( [self organization] )
- {
- [aMutableData appendCFormat:@"Organization: %@%s", [self organization], lineTerminator];
- }
-
- // We set the In-Reply-To header if we need to
- if ( [self headerValueForName: @"In-Reply-To"] )
- {
- [aMutableData appendCFormat:@"In-Reply-To: %@%s", [self headerValueForName: @"In-Reply-To"], lineTerminator];
- }
-
-
- // We now set all X-* headers
- allHeaderKeyEnumerator = [[self allHeaders] keyEnumerator];
-
- while ( (aKey = [allHeaderKeyEnumerator nextObject]) )
- {
- if ( [aKey hasPrefix: @"X-"] )
- {
- [aMutableData appendCFormat:@"%@: %@%s", aKey, [self headerValueForName: aKey], lineTerminator];
- }
- }
-
- //
- // We add our message header/body separator
- //
- [aMutableData appendData: [super dataUsingSendingMode: theMode]];
-
- return AUTORELEASE(aMutableData);
- }
-
-
- //
- // This method is used to add an extra header to the list of headers
- // of the message.
- //
- - (void) addHeader: (NSString *) theName
- withValue: (NSString *) theValue
- {
- [headers setObject: theValue forKey: theName];
- }
-
-
- - (id) headerValueForName: (NSString *) theName
- {
- return [headers objectForKey: theName];
- }
-
- - (NSDictionary *) allHeaders
- {
- return headers;
- }
-
- - (Folder *) folder
- {
- return folder;
- }
-
- - (void) setFolder: (Folder *) theFolder
- {
- folder = theFolder;
- }
-
-
- //
- // This method initalize all the headers of a message
- // from a raw data source.
- //
- // FIXME: Should we move most of the 'parsing' to the
- // Parser class?
- //
- - (void) setHeadersFromData: (NSData *) theHeaders
- {
- NSAutoreleasePool *pool;
- NSArray *allLines;
- int i;
-
- if (!theHeaders || [theHeaders length] == 0)
- {
- return;
- }
-
- // We initialize a local autorelease pool
- pool = [[NSAutoreleasePool alloc] init];
-
- // We MUST be sure to unfold all headers properly before
- // decoding the headers
- theHeaders = [MimeUtility unfoldLinesFromData: theHeaders];
-
- allLines = [theHeaders componentsSeparatedByCString: "\n"];
-
- for (i = 0; i < [allLines count]; i++)
- {
- NSData *aLine = [allLines objectAtIndex: i];
-
- // We stop if we found the header separator. (\n\n) since someone could
- // have called this method with the entire rawsource of a message.
- if ( [aLine length]==0 )
- {
- break;
- }
-
- if ([aLine hasCaseInsensitiveCPrefix: "Bcc"])
- {
- [Parser parseDestination:aLine
- forType:BCC
- inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Cc"])
- {
- [Parser parseDestination:aLine
- forType:CC
- inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Content-Disposition"])
- {
- [Parser parseContentDisposition:aLine inPart:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Content-Transfer-Encoding"])
- {
- [Parser parseContentTransferEncoding:aLine inPart:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Content-Type"])
- {
- [Parser parseContentType:aLine inPart:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Date"])
- {
- [Parser parseDate:aLine inMessage:self];
- }
- else if ( [aLine hasCaseInsensitiveCPrefix: "From"] &&
- ![aLine hasCaseInsensitiveCPrefix: "From -"] )
- {
- [Parser parseFrom:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Message-ID"])
- {
- [Parser parseMessageID:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "MIME-Version"])
- {
- [Parser parseMimeVersion:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Organization"])
- {
- [Parser parseOrganization:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "References"])
- {
- [Parser parseReferences: aLine
- inMessage: self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Reply-To"])
- {
- [Parser parseReplyTo:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Resent-From"])
- {
- [Parser parseResentFrom:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Resent-Bcc"])
- {
- [Parser parseDestination:aLine
- forType:RESENT_BCC
- inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Resent-Cc"])
- {
- [Parser parseDestination:aLine
- forType:RESENT_CC
- inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Resent-To"])
- {
- [Parser parseDestination:aLine
- forType:RESENT_TO
- inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Status"])
- {
- [Parser parseStatus:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "To"])
- {
- [Parser parseDestination:aLine
- forType:TO
- inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "X-Status"])
- {
- [Parser parseXStatus:aLine inMessage:self];
- }
- else if ([aLine hasCaseInsensitiveCPrefix: "Subject"])
- {
- [Parser parseSubject:aLine inMessage:self];
- }
- else
- {
- [Parser parseUnknownHeader:aLine inMessage:self];
- }
- }
-
- RELEASE(pool);
- }
-
- //
- //
- //
- - (void) setHeaders: (NSDictionary *) theHeaders
- {
- RELEASE(headers);
-
- headers = [[NSMutableDictionary alloc] initWithCapacity: [theHeaders count]];
- [headers addEntriesFromDictionary: theHeaders];
- }
-
-
- //
- // This method is used to optain tha raw source of a message.
- // It's returned as a NSData object. The returned data should
- // be the message like it's achived in a mbox format and it could
- // easily be decoded with Message: -initWithData.
- //
- // All subclasses of Message MUST implement this method.
- //
- - (NSData *) rawSource
- {
- if ( !rawSource )
- {
- [self subclassResponsibility: _cmd];
- return nil;
- }
-
- return rawSource;
- }
-
-
- //
- //
- //
- - (void) setRawSource: (NSData *) theRawSource
- {
- if ( theRawSource )
- {
- RETAIN(theRawSource);
- RELEASE(rawSource);
- rawSource = theRawSource;
- }
- else
- {
- DESTROY(rawSource);
- }
- }
-
- //
- //
- //
- - (NSCalendarDate *) resentDate
- {
- return [headers objectForKey: @"Resent-Date"];
- }
-
- - (void) setResentDate: (NSCalendarDate *) theResentDate
- {
- [headers setObject: theResentDate
- forKey: @"Resent-Date"];
- }
-
- - (InternetAddress *) resentFrom
- {
- return [headers objectForKey: @"Resent-From"];
- }
-
- - (void) setResentFrom: (InternetAddress *) theInternetAddress
- {
- [headers setObject: theInternetAddress
- forKey: @"Resent-From"];
- }
-
- - (NSString *) resentMessageID
- {
- return [headers objectForKey: @"Resent-Message-ID"];
- }
-
- - (void) setResentMessageID: (NSString *) theResentMessageID
- {
- [headers setObject: theResentMessageID
- forKey: @"Resent-Message-ID"];
- }
-
- - (NSString *) resentSubject
- {
- return [headers objectForKey: @"Resent-Subject"];
- }
-
- - (void) setResentSubject: (NSString *) theResentSubject
- {
- [headers setObject: theResentSubject
- forKey: @"Resent-Subject"];
- }
-
- @end
-
-
- //
- // Message's sorting category
- //
- @implementation Message (Comparing)
-
- - (int) compareAccordingToNumber: (Message *) aMessage
- {
- int num1, num2;
- num1 = [self messageNumber];
- num2 = [aMessage messageNumber];
- if (num1 < num2)
- {
- return NSOrderedAscending;
- }
- else if (num1 > num2)
- {
- return NSOrderedDescending;
- }
- else
- {
- return NSOrderedSame;
- }
- }
-
- - (int) reverseCompareAccordingToNumber: (Message *) aMessage
- {
- int num1, num2;
- num2 = [self messageNumber];
- num1 = [aMessage messageNumber];
- if (num1 < num2)
- {
- return NSOrderedAscending;
- }
- else if (num1 > num2)
- {
- return NSOrderedDescending;
- }
- else
- {
- return NSOrderedSame;
- }
- }
-
- - (int) compareAccordingToDate: (Message *) aMessage
- {
- NSDate *date1 = [self receivedDate];
- NSDate *date2 = [aMessage receivedDate];
- NSTimeInterval timeInterval;
-
- if (date1 == nil || date2 == nil)
- {
- return [self compareAccordingToNumber: aMessage];
- }
-
- timeInterval = [date1 timeIntervalSinceDate: date2];
-
- if (timeInterval < 0)
- {
- return NSOrderedAscending;
- }
- else if (timeInterval > 0)
- {
- return NSOrderedDescending;
- }
- else
- {
- return [self compareAccordingToNumber: aMessage];
- }
- }
-
- - (int) reverseCompareAccordingToDate: (Message *) aMessage
- {
- NSDate *date2 = [self receivedDate];
- NSDate *date1 = [aMessage receivedDate];
- NSTimeInterval timeInterval;
-
- if (date1 == nil || date2 == nil)
- {
- return [self reverseCompareAccordingToNumber: aMessage];
- }
-
- timeInterval = [date1 timeIntervalSinceDate: date2];
-
- if (timeInterval < 0)
- {
- return NSOrderedAscending;
- }
- else if (timeInterval > 0)
- {
- return NSOrderedDescending;
- }
- else
- {
- return [self reverseCompareAccordingToNumber: aMessage];
- }
- }
-
- - (int) compareAccordingToSender: (Message *) aMessage
- {
- InternetAddress *from1, *from2;
- NSString *fromString1, *fromString2;
- NSString *tempString;
- int result;
-
- from1 = [self from];
- from2 = [aMessage from];
-
- tempString = [from1 personal];
- if (tempString == nil || [tempString length] == 0)
- {
- fromString1 = [from1 address];
- if (fromString1 == nil)
- fromString1 = @"";
- }
- else
- {
- fromString1 = tempString;
- }
-
-
- tempString = [from2 personal];
- if (tempString == nil || [tempString length] == 0)
- {
- fromString2 = [from2 address];
- if (fromString2 == nil)
- fromString2 = @"";
- }
- else
- {
- fromString2 = tempString;
- }
-
- result = [fromString1 caseInsensitiveCompare: fromString2];
- if (result == NSOrderedSame)
- {
- return [self compareAccordingToNumber: aMessage];
- }
- else
- {
- return result;
- }
- }
-
- - (int) reverseCompareAccordingToSender: (Message *) aMessage
- {
- InternetAddress *from1, *from2;
- NSString *fromString1, *fromString2;
- NSString *tempString;
- int result;
-
- from2 = [self from];
- from1 = [aMessage from];
-
- tempString = [from1 personal];
- if (tempString == nil || [tempString length] == 0)
- {
- fromString1 = [from1 address];
- if (fromString1 == nil)
- fromString1 = @"";
- }
- else
- {
- fromString1 = tempString;
- }
-
-
- tempString = [from2 personal];
- if (tempString == nil || [tempString length] == 0)
- {
- fromString2 = [from2 address];
- if (fromString2 == nil)
- fromString2 = @"";
- }
- else
- {
- fromString2 = tempString;
- }
-
-
- result = [fromString1 caseInsensitiveCompare: fromString2];
-
- if (result == NSOrderedSame)
- {
- return [self reverseCompareAccordingToNumber: aMessage];
- }
- else
- {
- return result;
- }
- }
-
- - (int) compareAccordingToSubject: (Message *) aMessage
- {
- NSString *subject1 = [self subject];
- NSString *subject2 = [aMessage subject];
- int result;
-
- if (subject1 == nil)
- subject1 = @"";
- if (subject2 == nil)
- subject2 = @"";
-
- result = [subject1 caseInsensitiveCompare: subject2];
-
- if (result == NSOrderedSame)
- {
- return [self compareAccordingToNumber: aMessage];
- }
- else
- {
- return result;
- }
- }
-
- - (int) reverseCompareAccordingToSubject: (Message *) aMessage
- {
- NSString *subject2 = [self subject];
- NSString *subject1 = [aMessage subject];
- int result;
-
- if (subject1 == nil)
- subject1 = @"";
- if (subject2 == nil)
- subject2 = @"";
-
- result = [subject1 caseInsensitiveCompare: subject2];
-
- if (result == NSOrderedSame)
- {
- return [self compareAccordingToNumber: aMessage];
- }
- else
- {
- return result;
- }
- }
-
- - (int) compareAccordingToSize: (Message *) aMessage
- {
- int size1 = [self size];
- int size2 = [aMessage size];
-
-
- if (size1 < size2)
- {
- return NSOrderedAscending;
- }
- else if (size1 > size2)
- {
- return NSOrderedDescending;
- }
- else
- {
- return [self compareAccordingToNumber: aMessage];
- }
- }
-
- - (int) reverseCompareAccordingToSize: (Message *) aMessage
- {
- int size1 = [aMessage size];
- int size2 = [self size];
-
-
- if (size1 < size2)
- {
- return NSOrderedAscending;
- }
- else if (size1 > size2)
- {
- return NSOrderedDescending;
- }
- else
- {
- return [self reverseCompareAccordingToNumber: aMessage];
- }
- }
-
- @end
-
-
- //
- // Private methods
- //
- @implementation Message (Private)
-
- - (NSData *) _formatRecipientsWithType: (int) theType
- {
- NSMutableData *aMutableData;
- NSArray *anArray;
- int i;
-
- aMutableData = [[NSMutableData alloc] init];
- anArray = [self recipients];
-
- for (i = 0; i < [anArray count]; i++)
- {
- InternetAddress *anInternetAddress;
-
- anInternetAddress = [anArray objectAtIndex: i];
-
- if ([anInternetAddress type] == theType)
- {
- [aMutableData appendData: [anInternetAddress dataValue]];
- [aMutableData appendCString: ", "];
- }
- }
-
- if ( [aMutableData length] > 0)
- {
- [aMutableData setLength: [aMutableData length] - 2];
-
- return AUTORELEASE(aMutableData);
- }
- else
- {
- RELEASE(aMutableData);
-
- return nil;
- }
- }
-
- @end
-
-
-